]> git.r.bdr.sh - rbdr/map/blobdiff - Map/Presentation/Base Components/MapTextEditor.swift
Add some debouncing
[rbdr/map] / Map / Presentation / Base Components / MapTextEditor.swift
index d7aa9f86b18eb0cef014a3b7dbe40af2a1214abc..ff982034d1681f5247ef83d5ec5f02ef7b556710 100644 (file)
@@ -1,9 +1,36 @@
+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
 import Cocoa
 import SwiftUI
 
 class MapTextEditorController: NSViewController {
 
   @Binding var document: MapDocument
+  var highlightRanges: [Range<String.Index>] {
+    didSet {
+      updateHighlights()
+    }
+  }
+
+  var selectedRange: Int {
+    didSet {
+      updateHighlights()
+      focusOnResult()
+    }
+  }
+
   let onChange: () -> Void
 
   private let vertexRegex = MapParsingPatterns.vertex
@@ -16,9 +43,14 @@ class MapTextEditorController: NSViewController {
 
   private let changeDebouncer: Debouncer = Debouncer(seconds: 1)
 
-  init(document: Binding<MapDocument>, onChange: @escaping () -> Void) {
+  init(
+    document: Binding<MapDocument>, highlightRanges: [Range<String.Index>], selectedRange: Int,
+    onChange: @escaping () -> Void
+  ) {
     self._document = document
     self.onChange = onChange
+    self.highlightRanges = highlightRanges
+    self.selectedRange = selectedRange
     super.init(nibName: nil, bundle: nil)
   }
 
@@ -32,7 +64,7 @@ class MapTextEditorController: NSViewController {
 
     scrollView.translatesAutoresizingMaskIntoConstraints = false
 
-    textView.backgroundColor = .ui.background
+    textView.backgroundColor = .UI.background
     textView.allowsUndo = true
     textView.delegate = self
     textView.textStorage?.delegate = self
@@ -44,6 +76,42 @@ class MapTextEditorController: NSViewController {
 
   override func viewDidAppear() {
     self.view.window?.makeFirstResponder(self.view)
+    updateHighlights()
+  }
+
+  private var textView: NSTextView? {
+    return (view as? NSScrollView)?.documentView as? NSTextView
+  }
+
+  private func updateHighlights() {
+    if let textView {
+      if let textStorage = textView.textStorage {
+        textStorage.removeAttribute(
+          .backgroundColor, range: NSRange(location: 0, length: textStorage.length))
+
+        for (index, range) in highlightRanges.enumerated() {
+          let nsRange = NSRange(range, in: textStorage.string)
+
+          let color = index == selectedRange ? NSColor.Syntax.highlightMatch : NSColor.Syntax.match
+          textStorage.addAttribute(.backgroundColor, value: color, range: nsRange)
+        }
+
+        textView.needsDisplay = true
+
+      }
+    }
+  }
+
+  private func focusOnResult() {
+    if let textView {
+      if let textStorage = textView.textStorage {
+        if selectedRange < highlightRanges.count {
+          let range = highlightRanges[selectedRange]
+          let nsRange = NSRange(range, in: textStorage.string)
+          textView.scrollRangeToVisible(nsRange)
+        }
+      }
+    }
   }
 }
 
@@ -88,77 +156,77 @@ extension MapTextEditorController: NSTextStorageDelegate {
 
     for match in matches {
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
+        [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 1))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+        [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 2))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
+        [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 3))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.option], range: match.range(at: 4))
+        [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 4))
     }
 
     matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
 
     for match in matches {
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
+        [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 1))
       let arrowRange = match.range(at: 2)
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.symbol],
+        [.foregroundColor: NSColor.Syntax.symbol],
         range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3))
+        [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 3))
     }
 
     matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
 
     for match in matches {
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+        [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+        [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 2))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3))
+        [.foregroundColor: NSColor.Syntax.symbol], range: match.range(at: 3))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.number], range: match.range(at: 4))
+        [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 4))
     }
 
     matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
 
     for match in matches {
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+        [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+        [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 2))
     }
 
     matches = noteRegex.matches(in: textStorage.string, options: [], range: range)
 
     for match in matches {
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+        [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+        [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 2))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
+        [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 3))
     }
 
     matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
 
     for match in matches {
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+        [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
+        [.foregroundColor: NSColor.Syntax.number], range: match.range(at: 2))
     }
 
     matches = groupRegex.matches(in: textStorage.string, options: [], range: range)
 
     for match in matches {
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
+        [.foregroundColor: NSColor.Syntax.option], range: match.range(at: 1))
       textStorage.addAttributes(
-        [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
+        [.foregroundColor: NSColor.Syntax.vertex], range: match.range(at: 2))
     }
   }
 }
@@ -166,16 +234,25 @@ extension MapTextEditorController: NSTextStorageDelegate {
 struct MapTextEditor: NSViewControllerRepresentable {
 
   @Binding var document: MapDocument
+  var highlightRanges: [Range<String.Index>]
+  var selectedRange: Int
   var onChange: () -> Void = {}
 
   func makeNSViewController(
     context: NSViewControllerRepresentableContext<MapTextEditor>
   ) -> MapTextEditorController {
-    return MapTextEditorController(document: $document, onChange: onChange)
+    return MapTextEditorController(
+      document: $document, highlightRanges: highlightRanges, selectedRange: selectedRange,
+      onChange: onChange)
   }
 
   func updateNSViewController(
     _ nsViewController: MapTextEditorController,
     context: NSViewControllerRepresentableContext<MapTextEditor>
-  ) {}
+  ) {
+    nsViewController.highlightRanges = highlightRanges
+    if nsViewController.selectedRange != selectedRange {
+      nsViewController.selectedRange = selectedRange
+    }
+  }
 }